#================================================
package MSN3::Circle;
#================================================

use strict;
use warnings;

# For Debugging
use Data::Dumper;

# General
use MIME::Base64;
use Digest::MD5 qw(md5 md5_hex md5_base64);
use XML::Simple;
use URI::Escape;
use Switch;
use POSIX;

sub checksum { my $o = tell(DATA); seek DATA,0,0; local $/; my $t = unpack("%32C*",<DATA>) % 65535;seek DATA,$o,0; return $t};

sub new
{
    my $class = shift;
    my $msn = shift;
    my $to = shift;

    my $self =
    {
        MSN             => $msn,

        MultiParty      => 1,
        FileNumber      => 0,

        To              => $to,

        Roster          => {},

        @_
    };
    bless($self, $class);

    $self->fileno();

    my ($network, $handle) = split(/:/, $self->{To}, 2);
    if( $network ne "10" )
    {
        $self->addMember($handle, $network);
        $self->{MultiParty} = 0;
        #$self->create();

        $self->getNotification()->{Circles}->{ $to } = $self;
    }

    return $self;
}

sub DESTROY
{
    my $self = shift;

    # placeholder for possible destructor code
}

sub AUTOLOAD
{
    my $self = shift;

    my $method = $MSN3::Circle::AUTOLOAD;

    if( $method =~ /CMD_(.*)$/ )
    {
        $self->error( "$1 not handled in MSN3::Circle" );
    }
    else
    {
        $self->error( "method $method not defined" ) if( $self->{MSN}->{Error} );
    }
}

sub fileno
{
    my $self = shift;
    return $self->{FileNumber} if($self->{FileNumber} > 0);

    my $fileno = 0;
    my $i = 1;
    while(!$fileno)
    {
        my $blnFound = 0;
        foreach my $convo (values %{$self->getNotification()->{Circles}})
        {
            if($convo->{FileNumber} == $i)
            {
                $blnFound = 1;
            }
        }
        $fileno = $i if(!$blnFound);
    }

    $self->{FileNumber} = $fileno;

    return $fileno;
}

#================================================
# Error / Debugging
#================================================

sub debug
{
    my $self = shift;

    return $self->{MSN}->debug( @_ );
}

sub error
{
    my $self = shift;

    return $self->{MSN}->error( @_ );
}

#================================================
# Create multi party
#================================================

sub create
{
    my $self = shift;

    my $body = "";
    my $header =
            "Routing: 1.0\r\n".
            "To: 10:00000000-0000-0000-0000-000000000000\@live.com\r\n".
            "From: 1:". $self->{MSN}->{Handle} .";epid=". $self->{MSN}->{MachineGUID} ."\r\n\r\n".
            "Reliability: 1.0\r\n".
            "Stream: 0\r\n".
            "Segment: 0\r\n\r\n".
            "Publication: 1.0\r\n".
            "Uri: /circle\r\n".
            "Content-Type: application/multiparty+xml\r\n".
            "Content-Length: ". length($body);

    my $TrID = $self->sendraw( 'PUT', length($header."\r\n\r\n".$body) ."\r\n". $header ."\r\n\r\n". $body );
    $self->getNotification()->{PendingCircles}->{ $TrID } = $self;

    return 1;
}

sub accept
{
    my $self = shift;

    my $body = '<circle><roster><id>IM</id><user><id>1:'. $self->{MSN}->{Handle} .'</id></user></roster></circle>';
    my $header =
            "Routing: 1.0\r\n".
            "To: ". $self->{To} ."\r\n".
            "From: 1:". $self->{MSN}->{Handle} .";epid=". $self->{MSN}->{MachineGUID} ."\r\n\r\n".
            "Reliability: 1.0\r\n".
            "Stream: 1\r\n".
            "Flags: ACK\r\n\r\n".
            "Publication: 1.0\r\n".
            "Uri: /circle\r\n".
            "Content-Type: application/circles+xml\r\n".
            "Content-Length: ". length($body);

    $self->sendraw('PUT', length($header."\r\n\r\n".$body) ."\r\n". $header ."\r\n\r\n".$body);

    return 1;
}

sub inviteall
{
    my $self = shift;

    foreach my $user (keys %{$self->{Roster}})
    {
        $self->invite($self->{Roster}->{$user}.':'.$user);
    }

    return 1;
}

#================================================
# Send functions
#================================================

sub send
{
    my $self = shift;

    return $self->getNotification()->send( @_ );
}

sub sendraw
{
    my $self = shift;

    return $self->getNotification()->sendraw( @_ );
}

sub sendput
{
    my $self = shift;

    return $self->getNotification()->sendput( @_ );
}

sub sendsdg
{
    my $self = shift;

    return $self->getNotification()->sendsdg( @_ );
}

sub senddel
{
    my $self = shift;

    return $self->getNotification()->senddel( @_ );
}

sub _send
{
    my $self = shift;

    return $self->getNotification()->_send( @_ );
}

#================================================
# Dispatch a circle event
#================================================

sub dispatch
{
    my $self = shift;
    my $header = shift || return;
    my $msg = shift || '';

    switch($header->{'Message-Type'} || '')
    {
        case "Signal/P2P"
        {
            $self->{MSN}->getNotification()->{P2P}->dispatch( $header, $msg );
        }
        case "Data"
        {
            $self->{MSN}->getNotification()->{P2P}->dispatch( $header, $msg );
        }
        case "Control/Typing"
        {
            $self->{MSN}->triggerEvent( $self, "Typing", $header->{From} );
        }
        case "Text"
        {
            $header->{'X-MMS-IM-Format'} =~ /FN=(.*?);\s+EF=(.*?);\s+CO=(.*?);\s+CS=(.*?);\s+PF=(.*?)/g;
            my %style = ( Font => $1, Effect => $2, Color => $3, CharacterSet => $4, PitchFamily => $5 );
            $self->{MSN}->triggerEvent( $self, "Message", $header->{From}, $msg, %style );
        }
        else
        {
            $self->debug( "Unknown Message Type: ". $header->{'Message-Type'} );
        }
    }
}

#================================================
# MSN Server messages handled by Circle
#================================================

sub addMember
{
    my $self = shift;
    my $handle = shift || return;
    my $network = shift || 1;

    if( !defined $self->{Roster}->{ $handle } )
    {
        $self->{Roster}->{ $handle } = $network;

        $self->{MSN}->triggerEvent($self, "MemberJoined", $network, $handle);

        if( scalar(keys %{$self->{Roster}}) > 1 )
        {
            $self->{MSN}->triggerEvent($self, "RoomUpdated", $network, $handle);
        }
        else
        {
            $self->{MSN}->triggerEvent($self, "RoomOpened", $network, $handle);
        }
    }

    return 1;
}

sub removeMember
{
    my $self = shift;
    my $handle = shift || return;
    my $network = shift || return;

    if( defined $self->{Roster}->{ $handle } )
    {
        delete( $self->{Roster}->{ $handle } );

        $self->{MSN}->triggerEvent($self, "MemberLeft", $network, $handle);

        if( scalar(keys %{$self->{Roster}}) == 0 )
        {
            $self->{MSN}->triggerEvent($self, "RoomClosed", $network, $handle);
            delete( $self->getNotification()->{Circles}->{ $self->{'To-Network'} .':'. $self->{To} } );
        }
        else
        {
            $self->{MSN}->triggerEvent($self, "RoomUpdated", $network, $handle);
        }
    }

    return 1;
}

#================================================
# Misc functions
#================================================

sub getNotification
{
    my $self = shift;

    return $self->{MSN}->{Notification};
}

sub sendTyping
{
    my $self = shift;

    my $body = '';
    $self->sendsdg(
        {
            Routing           => '1.0',
            To                => $self->{To},
            From              => '1:'. $self->{MSN}->{Handle} .";epid=". $self->{MSN}->{MachineGUID},
        },
        {
            Reliability       => '1.0',
        },
        {
            Messaging         => '2.0',
            'Message-Type'    => 'Control/Typing',
            'Content-Length'  => length($body),
        },
        $body
    );

    return 1
}

sub sendMessage
{
    my $self = shift;
    my $response = shift || return $self->error( "You must pass the message you want to send" );

    my $style = { (%{$self->{MSN}->{MessageStyle}}), @_ };
    $style->{Font} = uri_escape( $style->{Font} );

    my $chunks = ceil( length($response) / 1400 );
    my $guid = $self->CreateGUID();

    $self->sendTyping();

    for (my $chunk = 0; $chunk < $chunks; $chunk++)
    {
        my %extra = ();
        $extra{'P4-Context'} = $style->{Name} if(exists $style->{Name});
        $extra{'Message-ID'} = $guid;

        $extra{'Chunks'} = $chunks if($chunk == 0);
        $extra{'Chunk'} = $chunk if($chunk != 0);

        my $body = substr($response, $chunk * 1400, 1400);
        $self->sendsdg(
            {
                Routing           => '1.0',
                To                => $self->{To},
                From              => '1:'. $self->{MSN}->{Handle} .";epid=". $self->{MSN}->{MachineGUID},
            },
            {
                Reliability       => '1.0',
            },
            {
                Messaging         => '2.0',
                'Message-Type'    => 'Text',
                'Content-Length'  => length($body),
                'Content-Type'    => 'text/plain; charset=UTF-8',
                'X-MMS-IM-Format' => 'FN='. $style->{Font} .'; EF='. $style->{Effect} .'; CO='. $style->{Color} .'; CS='. $style->{CharacterSet} .'; PF='. $style->{PitchFamily},
                %extra
            },
            $body
        );
    }
}

sub leave
{
    my $self = shift;


    my $body = '';
    $self->senddel(
        {
            Routing           => '1.0',
            To                => $self->{To},
            From              => '1:'. $self->{MSN}->{Handle} .";epid=". $self->{MSN}->{MachineGUID},
        },
        {
            Reliability       => '1.0',
            Stream            => 0,
        },
        {
            Publication       => '2.0',
            Uri               => '/circle/roster(IM)/user(1:'. $self->{MSN}->{Handle} .')',
            'Content-Type'    => 'application/circles+xml',
            'Content-Length'  => length($body),
        },
        $body
    );

    return 1;
}

sub invite
{
    my $self = shift;
    my $handle = shift;

    if( $self->{MultiParty} eq 0 )
    {
        my ($network, $handle) = split(/:/, $handle, 2);
        $self->{Roster}->{ $handle } = $network;
        $self->create();
    }
    else
    {
        my $body = '<circle><roster><id>IM</id><user><id>'. $handle .'</id></user></roster></circle>';
        my $header =
                "Routing: 1.0\r\n".
                "To: ". $self->{To} .";path=IM\r\n".
                "From: 1:". $self->{MSN}->{Handle} .";epid=". $self->{MSN}->{MachineGUID} ."\r\n\r\n".
                "Reliability: 1.0\r\n".
                "Stream: 0\r\n".
                "Segment: 0\r\n\r\n".
                "Publication: 1.0\r\n".
                "Uri: /circle\r\n".
                "Content-Type: application/multiparty+xml\r\n".
                "Content-Length: ". length($body);

        $self->sendraw('PUT', length($header."\r\n\r\n".$body) ."\r\n". $header ."\r\n\r\n".$body);
    }

    return 1;
}

sub CreateGUID
{
    #{DWORD-WORD-WORD-WORD-WORD.DWORD}
    return sprintf("{%08X-%04X-%04X-%04X-%04X%08X}",
    rand(2294967295),rand(32767),rand(32767),rand(32767),rand(32767),rand(2294967295));
}

return 1;
__DATA__
